﻿'''   Copyright 2010 Tom Mealey
'''
'''   Licensed under the Apache License, Version 2.0 (the "License");
'''   you may not use this file except in compliance with the License.
'''   You may obtain a copy of the License at
'''
'''       http://www.apache.org/licenses/LICENSE-2.0

'''   Unless required by applicable law or agreed to in writing, software
'''   distributed under the License is distributed on an "AS IS" BASIS,
'''   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
'''   See the License for the specific language governing permissions and
'''   limitations under the License.

Imports System.Runtime.InteropServices
'Imports System.IO

Public Class serControl
#Region "API Constants"
    Private Const DIGCF_PRESENT As Long = &H2
    Private Const DIGCF_DEVICEINTERFACE As Long = &H10
#End Region

#Region "API Enums"
    Private Enum DEVICEPROPERTYINDEX
        SPDRP_ADDRESS = (&H1C)
        SPDRP_BUSNUMBER = (&H15)
        SPDRP_BUSTYPEGUID = (&H13)
        SPDRP_CAPABILITIES = (&HF)
        SPDRP_CHARACTERISTICS = (&H1B)
        SPDRP_CLASS = (&H7)
        SPDRP_CLASSGUID = (&H8)
        SPDRP_COMPATIBLEIDS = (&H2)
        SPDRP_CONFIGFLAGS = (&HA)
        SPDRP_DEVICEDESC = &H0
        SPDRP_DEVTYPE = (&H19)
        SPDRP_DRIVER = (&H9)
        SPDRP_ENUMERATOR_NAME = (&H16)
        SPDRP_EXCLUSIVE = (&H1A)
        SPDRP_FRIENDLYNAME = (&HC)
        SPDRP_HARDWAREID = (&H1)
        SPDRP_LEGACYBUSTYPE = (&H14)
        SPDRP_LOCATION_INFORMATION = (&HD)
        SPDRP_LOWERFILTERS = (&H12)
        SPDRP_MFG = (&HB)
        SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = (&HE)
        SPDRP_SECURITY = (&H17)
        SPDRP_SECURITY_SDS = (&H18)
        SPDRP_SERVICE = (&H4)
        SPDRP_UI_NUMBER = (&H10)
        SPDRP_UPPERFILTERS = (&H11)
    End Enum

    Private Enum REGPROPERTYTYPES
        REG_BINARY = 3
        REG_DWORD = 4
        REG_DWORD_BIG_ENDIAN = 5
        REG_DWORD_LITTLE_ENDIAN = 4
        REG_EXPAND_SZ = 2
        REG_MULTI_SZ = 7
        REG_SZ = 1
    End Enum

    Private Enum DICS_FLAG As Integer
        DICS_FLAG_GLOBAL = &H1
        DICS_FLAG_CONFIGSPECIFIC = &H2
        DICS_FLAG_CONFIGGENERAL = &H4
    End Enum

    Private Enum DIREG As Integer
        DIREG_DEV = &H1
        DIREG_DRV = &H2
        DIREG_BOTH = &H4
    End Enum

    Private Enum KEY_T As Integer
        KEY_QUERY_VALUE = &H1
        KEY_SET_VALUE = &H2
        KEY_CREATE_SUB_KEY = &H4
        KEY_ENUMERATE_SUB_KEYS = &H8
        KEY_NOTIFY = &H10
        KEY_CREATE_LINK = &H20
        KEY_WOW64_32KEY = &H200
        KEY_WOW64_64KEY = &H100
        KEY_WOW64_RES = &H300
    End Enum

    Private Enum KeyType As Long
        HKEY_CLASSES_ROOT = &H80000000
        'HKEY_CURRENT_CONFIG
        HKEY_CURRENT_USER = &H80000001
        HKEY_LOCAL_MACHINE = &H80000002
        'HKEY_PERFORMANCE_DATA
        'HKEY_PERFORMANCE_NLSTEXT
        'HKEY_PERFORMANCE_TEXT
        HKEY_USERS = &H80000003
    End Enum
#End Region

#Region "API structures"
    <StructLayout(LayoutKind.Sequential)> _
        Structure SP_DEVICE_INTERFACE_DATA
        Public cbSize As Integer
        Public InterfaceClassGuid As Guid
        Public Flags As Integer
        Public Reserved As IntPtr
    End Structure
#End Region

#Region "API Functions & Subs"
    Private Declare Auto Function SetupDiGetClassDevs Lib "setupapi.dll" Alias "SetupDiGetClassDevsW" (ByRef ClassGuid As Guid, ByVal Enumerator As Integer, ByVal hwndParent As Integer, ByVal Flags As UInteger) As IntPtr
    Private Declare Auto Function SetupDiEnumDeviceInfo Lib "setupapi.dll" (ByVal List As IntPtr, ByVal Index As UInt32, ByRef Device As SP_DEVICE_INTERFACE_DATA) As Boolean
    Private Declare Auto Function SetupDiGetDeviceRegistryProperty Lib "setupapi.dll" Alias "SetupDiGetDeviceRegistryPropertyW" (ByVal DeviceInfoSet As UInt32, ByRef DeviceInfoData As SP_DEVICE_INTERFACE_DATA, ByVal Prop As DEVICEPROPERTYINDEX, ByRef PropertyRegDataType As REGPROPERTYTYPES, ByVal PropertyBuffer As Text.StringBuilder, ByVal PropertyBufferSize As UInt32, ByRef RequiredSize As UInt32) As UInt32
    Private Declare Auto Function SetupDiOpenDevRegKey Lib "setupapi.dll" (ByVal hDeviceInfo As IntPtr, ByRef deviceInfoData As SP_DEVICE_INTERFACE_DATA, ByVal Scope As DICS_FLAG, ByVal hwprofile As Integer, ByVal parameterRegistryValueKind As DIREG, ByVal samDesired As KEY_T) As IntPtr
    Private Declare Ansi Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As IntPtr, ByVal lpValueName As String, ByVal lpReserved As Integer, ByRef lpType As Integer, ByVal lpData As System.Text.StringBuilder, ByRef lpcbData As Integer) As Integer
    Private Declare Auto Function RegCloseKey Lib "advapi32.dll" (ByVal hHey As IntPtr) As Long
#End Region

    Private Shared GUID_DEVCLASS_PORTS As New Guid(&H4D36E978L, &HE325, &H11CE, &HBF, &HC1, &H8, &H0, &H2B, &HE1, &H3, &H18)

    Public Class serPort
        Public Sub New(ByVal PortName As String, ByVal PortDescription As String, Optional ByVal HardwareName As String = "")
            Me.friendlyName = PortDescription
            Me.dosName = PortName
            Me.devicePath = HardwareName
        End Sub

        Public Overrides Function ToString() As String
            Return Me.dosName & " <" & Me.friendlyName & ">"
        End Function

        Dim dosName As String
        Dim friendlyName As String
        Dim devicePath As String
        Public ReadOnly Property Name() As String
            Get
                Return Me.dosName
            End Get
        End Property
    End Class

    Public Shared Function GetPorts() As serPort()
        'Temporary method using native .net stuff.
        Dim retPorts As serPort() = {}
        For Each port As String In IO.Ports.SerialPort.GetPortNames
            ReDim Preserve retPorts(retPorts.GetUpperBound(0) + 1)
            retPorts(retPorts.GetUpperBound(0)) = New serPort(port, port, port)
        Next
        Return retPorts
    End Function

    Public Shared Function GetPorts2() As serPort()
        Dim hDevInfo As IntPtr = SetupDiGetClassDevs(GUID_DEVCLASS_PORTS, 0, IntPtr.Zero, DIGCF_PRESENT Or DIGCF_DEVICEINTERFACE)
        If hDevInfo.ToInt32 = -1 Then
            MsgBox("SetupDiGetClassDevs failed (" & Err.LastDllError & ")")
            Return Nothing
        End If
        Dim retSerPort() As serPort = {}
        Dim bMoreItems As Boolean = True
        Dim nIndex As Integer = 0
        Dim devInfo As SP_DEVICE_INTERFACE_DATA
        While bMoreItems
            devInfo.cbSize = 24 + IntPtr.Size
            bMoreItems = SetupDiEnumDeviceInfo(hDevInfo, nIndex, devInfo)
            If Not bMoreItems Then
                Dim sE As Integer = Err.LastDllError
                If sE <> 259 Then
                    MsgBox("SetupDiEnumDeviceInfo failed (" & Err.LastDllError & ")")
                End If
            End If
            If bMoreItems Then
                Dim bAdded As Boolean = False
                Dim hDeviceKey As IntPtr = SetupDiOpenDevRegKey(hDevInfo, devInfo, DICS_FLAG.DICS_FLAG_GLOBAL, 0, DIREG.DIREG_DEV, KEY_T.KEY_QUERY_VALUE)
                If CBool(hDeviceKey) Then
                    Dim szPortName As New System.Text.StringBuilder(Chr(0) & Space(255))
                    Dim dwType As KeyType = 0
                    Dim ret As Integer
                    ret = RegQueryValueEx(hDeviceKey, "PortName", IntPtr.Zero, dwType, szPortName, 1024)
                    dwType = 0
                    Dim szFriendlyName As New System.Text.StringBuilder(Chr(0) & Space(255))
                    SetupDiGetDeviceRegistryProperty(hDevInfo, devInfo, DEVICEPROPERTYINDEX.SPDRP_DEVICEDESC, dwType, szFriendlyName, szFriendlyName.Length, szFriendlyName.Length)
                    szFriendlyName.Replace(Chr(0), "") 'stupid null terminated strings...
                    Dim szHardwareName As New System.Text.StringBuilder(Chr(0) & Space(255))
                    SetupDiGetDeviceRegistryProperty(hDevInfo, devInfo, DEVICEPROPERTYINDEX.SPDRP_HARDWAREID, dwType, szHardwareName, szHardwareName.Length, szHardwareName.Length)
                    szHardwareName.Replace(Chr(0), "")
                    Console.WriteLine(szFriendlyName.ToString)
                    Console.WriteLine(szHardwareName.ToString)
                    ReDim Preserve retSerPort(retSerPort.GetLength(0))
                    retSerPort(retSerPort.GetUpperBound(0)) = New serPort(szPortName.ToString, szFriendlyName.ToString, szHardwareName.ToString)
                Else
                    MsgBox("SetupDiOpenDevRegKey Failed! (" & Err.LastDllError & ")")
                End If
                RegCloseKey(hDeviceKey)
            End If
            nIndex += 1
        End While
        Return retSerPort
    End Function

    Private WithEvents realPort As IO.Ports.SerialPort

    Public Sub New(ByVal Port As String)
        realPort = New IO.Ports.SerialPort(Port)
        realPort.ReadTimeout = 500
        realPort.ReceivedBytesThreshold = 50
        realPort.ReadBufferSize = 115200
    End Sub

    Public Event DataRecieved(ByVal bytes As Integer)

    Public Sub Open()
        realPort.Open()
    End Sub

    Public Function isOpen() As Boolean
        Return realPort.IsOpen
    End Function

    Public Function Read(ByVal bytes As Integer) As String
        Dim data As String = ""
        If bytes <= realPort.BytesToRead Then
            realPort.Read(data, 0, bytes)
            Return data
        Else
            Console.WriteLine("Attempted to read too many byets!")
            Return ""
        End If
    End Function

    Public Sub New(ByVal Port As serPort)
        Me.New(Port.Name)
    End Sub

    Private Sub realPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles realPort.DataReceived
        'TODO: Pass recieved data to all TCP connections.
        MsgBox(realPort.BytesToRead)
        e.
        RaiseEvent DataRecieved(realPort.BytesToRead)
    End Sub

    Protected Overrides Sub Finalize()
        If Not realPort Is Nothing Then
            If realPort.IsOpen Then
                realPort.Close()
            End If
            realPort = Nothing
        End If
        MyBase.Finalize()
    End Sub
End Class
